
/*
  =======================================================================================
  File: 
        dt_btn_util.c

  Description: 
        The implementation for phisical button utility functions.

  Author:
       Delphi Tang (唐佐林)
       http://www.dt4sw.com/

  Revision History:
       V0.0.1 (Initial Version)
       V0.0.2 ( 
                1. Do refactory for the interface to encapsulate the vendor SDK details
                2. Move event handler call to a special thread to avoid heavy process in ISR
                3. Trigger long-pressed-event after pressed-event for about 2.5 seconds 
              )
       V0.0.3 ( 
                1. Bug fix for NOT trigger long-pressed-event without pressed-event registration
                2. Support phisical key S1, S2 and User to trigger independent event 
              )
  =======================================================================================
*/

#include <unistd.h>
#include <string.h>
#include "cmsis_os2.h"
#include "hi_systick.h"
#include "hi_adc.h"
#include "wifiiot_gpio.h"
#include "wifiiot_gpio_ex.h"
#include "dt_btn_util.h"

#define BUTTON_STACK_SIZE   2048
#define PRESS_INTERVAL      20000
#define LOOP_INTERVAL       40000
#define LONG_PRESS_INTERVAL 64
#define LONG_PRESS_END      0xFFFF
#define INDEX_ERR           -1
#define MAX_KEY_NUM         SSU_None

enum
{
    ADC_USR_MIN = 5,
    ADC_USR_MAX = 228,
    ADC_S1_MIN,
    ADC_S1_MAX  = 512, 
    ADC_S2_MIN,
    ADC_S2_MAX  = 854
};

enum 
{
    SSU_USR = 15,
    SSU_S1,
    SSU_S2,
    SSU_None
};

typedef struct
{
    const char* name;
    unsigned int index;
    unsigned int event;
    PBtnCallback callback;
} ButtonInfo;

typedef struct
{
    int pressClicked;
    int longPressClicked;
    int longPressInterval;
    int releaseClicked;
} ButtonClicked;

static volatile int gToClick = 0;
static volatile int gIsInit = 0;
static const char* gBtnName[] = {
    "GPIO_0",  "GPIO_1",  "GPIO_2",  "GPIO_3",  "GPIO_4",
    "GPIO_5",  "GPIO_6",  "GPIO_7",  "GPIO_8",  "GPIO_9",
    "GPIO_10", "GPIO_11", "GPIO_12", "GPIO_13", "GPIO_14",
    "USR",     "S1",      "S2",
    NULL
};

static volatile ButtonInfo gBtnInfo[MAX_KEY_NUM] = {0};
static volatile ButtonClicked gBtnClicked[MAX_KEY_NUM] = {0};

static void OnButtonPressed(char* arg);
static void OnButtonReleased(char* arg);

static int GetIndex(const char* name)
{
    int ret = INDEX_ERR;
    int i = 0;

    while( gBtnName[i] && name )
    {
        if( strcmp(gBtnName[i], name) == 0 )
        {
            ret = i;
            break;
        }

        i++;
    }

    return ret;
}

static int GetSSU(void)
{
    unsigned short data = 0;
    int ret = SSU_None;

    if( hi_adc_read(HI_ADC_CHANNEL_2, &data, HI_ADC_EQU_MODEL_4, HI_ADC_CUR_BAIS_DEFAULT, 0) == 0 )
    {
        if( (ADC_USR_MIN <= data) && (data <= ADC_USR_MAX) )  ret = SSU_USR;
        if( (ADC_S1_MIN  <= data) && (data <= ADC_S1_MAX ) )  ret = SSU_S1;
        if( (ADC_S2_MIN  <= data) && (data <= ADC_S2_MAX ) )  ret = SSU_S2;
    }

    return ret;
}

static void OnButtonPressed(char* arg)
{
    static volatile hi_u64 sHisTick = 0;
    WifiIotIoName gpio = (WifiIotIoName)arg;
    hi_u64 tick = hi_systick_get_cur_tick();
    
    gToClick = (tick - sHisTick) > PRESS_INTERVAL;
    
    if( gToClick )
    {
        sHisTick = tick;

        gBtnClicked[gpio].pressClicked = 1;

        GpioRegisterIsrFunc(gpio,
                            WIFI_IOT_INT_TYPE_EDGE, 
                            WIFI_IOT_GPIO_EDGE_RISE_LEVEL_HIGH,
                            OnButtonReleased, arg);
    }          
}

static void OnButtonReleased(char* arg)
{
    WifiIotIoName gpio = (WifiIotIoName)arg;
    
    if( gToClick )
    {
        gBtnClicked[gpio].releaseClicked = 1;

        GpioRegisterIsrFunc(gpio,
                            WIFI_IOT_INT_TYPE_EDGE, 
                            WIFI_IOT_GPIO_EDGE_FALL_LEVEL_LOW,
                            OnButtonPressed, arg);
    }
}

static void SSUEventTrigger(void)
{
    static hi_u64 sHisTick = 0;
    static int sPreKey = SSU_None;

    int curKey = GetSSU();

    if( (sPreKey == SSU_None) && (curKey != SSU_None) )
    {
        hi_u64 tick = hi_systick_get_cur_tick();
        int toClick = (tick - sHisTick) > PRESS_INTERVAL;

        if( toClick )
        {
            gBtnClicked[curKey].pressClicked = 1;

            sPreKey = curKey;

            sHisTick = tick;
        }
    }
    else if( (sPreKey != SSU_None) && (curKey == SSU_None) )
    {
        gBtnClicked[sPreKey].releaseClicked = 1;

        sPreKey = curKey;
    }
}

static void EventHandler(void)
{
    int i = 0;

    for(i=0; i<MAX_KEY_NUM; i++)
    {
        const char* name = gBtnInfo[i].name;

        if( gBtnClicked[i].pressClicked )
        {
            if( gBtnInfo[i].event & Pressed )
                gBtnInfo[i].callback(name, Pressed);
                
            gBtnClicked[i].pressClicked = 0;
            gBtnClicked[i].longPressInterval = 0;
        }

        if( gBtnClicked[i].longPressInterval < LONG_PRESS_END )
        {
            gBtnClicked[i].longPressInterval++;
        }

        if( gBtnClicked[i].longPressInterval == LONG_PRESS_INTERVAL )
        {
            gBtnClicked[i].longPressClicked = 1;
        }

        if( gBtnClicked[i].longPressClicked )
        {
            if( gBtnInfo[i].event & LongPressed )
                gBtnInfo[i].callback(name, LongPressed);

            gBtnClicked[i].longPressClicked = 0;
            gBtnClicked[i].longPressInterval = LONG_PRESS_END;
        }

        if( gBtnClicked[i].releaseClicked )
        {
            if( gBtnInfo[i].event & Released ) 
                gBtnInfo[i].callback(name, Released);

            gBtnClicked[i].releaseClicked = 0;
            gBtnClicked[i].longPressInterval= LONG_PRESS_END;
        }
    }
}

static void* DTButton_Task(const char* arg)
{
    while( gIsInit )
    {
        SSUEventTrigger();
        EventHandler();
        usleep(LOOP_INTERVAL);
    }
    
    return (void*)arg;
}

int DTButton_Init(void)
{
    int ret = (int)GpioInit();

    if( ret == (int)HI_ERR_GPIO_REPEAT_INIT )
    {
        ret = 0;
    }

    if( !ret && !gIsInit )
    {
        int i = 0;
        osThreadAttr_t attr = {0};

        for(i=0; i<MAX_KEY_NUM; i++)
        {
            gBtnClicked[i].longPressInterval = LONG_PRESS_END;
        }

        attr.name = "DTButton_Task";
        attr.attr_bits = 0U;
        attr.cb_mem = NULL;
        attr.cb_size = 0U;
        attr.stack_mem = NULL;
        attr.stack_size = BUTTON_STACK_SIZE;
        attr.priority = osPriorityNormal;

        ret += (osThreadNew((osThreadFunc_t)DTButton_Task, NULL, &attr) == NULL);

        gIsInit = (ret == 0);
    }

    return ret;
}

void DTButton_Deinit(void)
{
    gIsInit = 0;
}

int DTButton_Enable(const char* name, PBtnCallback callback, unsigned int event)
{
    int ret = -1;

    if( callback )
    {
        int index = name ? GetIndex(name) : INDEX_ERR;

        if( (WIFI_IOT_IO_NAME_GPIO_0 <= index) && (index < WIFI_IOT_IO_NAME_MAX) )
        {   
            ret  = IoSetFunc((WifiIotIoName)index, 0);
            ret += GpioSetDir((WifiIotIoName)index, WIFI_IOT_GPIO_DIR_IN);
            ret += IoSetPull((WifiIotIoName)index, WIFI_IOT_IO_PULL_UP);
            ret += GpioRegisterIsrFunc((WifiIotIoName)index,
                                WIFI_IOT_INT_TYPE_EDGE, 
                                WIFI_IOT_GPIO_EDGE_FALL_LEVEL_LOW,
                                OnButtonPressed, (char*)index);                   
        }
        else if( (SSU_USR <= index) && (index < MAX_KEY_NUM) )
        {
            ret = 0;
        }

        if( ret == 0 )
        {
            gBtnInfo[index].name = name;
            gBtnInfo[index].index = index;
            gBtnInfo[index].event = event;
            gBtnInfo[index].callback = callback;
        }
    }

    return ret;
}

void DTButton_Disable(const char* name)
{
    int gpio = name ? GetIndex(name) : INDEX_ERR;
    
    if( gpio != INDEX_ERR )
    {
        gBtnInfo[gpio].name = 0;
        gBtnInfo[gpio].index = 0;
        gBtnInfo[gpio].event = 0;
        gBtnInfo[gpio].callback = 0;
    }
}